Skip to content

perf: cross-layer performance pass (dashboard, Python, Rust)#213

Merged
pratyush618 merged 24 commits into
masterfrom
perf/optimization-pass
May 29, 2026
Merged

perf: cross-layer performance pass (dashboard, Python, Rust)#213
pratyush618 merged 24 commits into
masterfrom
perf/optimization-pass

Conversation

@pratyush618

@pratyush618 pratyush618 commented May 29, 2026

Copy link
Copy Markdown
Collaborator

A low-risk-plus-moderate performance pass across all three layers, targeting per-job / per-enqueue / per-render hot paths. 17 focused commits, no public API or contract changes.

Dashboard — render & refetch overhead

  • Disable refetchOnWindowFocus and add per-query staleTime for slow-moving data (no thundering-herd refetch on tab focus)
  • Memoize the JobTable error-column scan, the throughput sparkline geometry (paths no longer recompute on hover; unique useId gradient), and the JobIntegrations links array
  • React.memo(DataTable) + stable row callbacks; adaptive LastRefreshed interval (no more forced 1s header re-render); lazy-enable job-detail tab queries; ThemeProvider reads localStorage once

Python — enqueue & dispatch hot paths

  • Bug fix: the native-async executor deserialized with a hardcoded cloudpickle.loads, ignoring @task(serializer=...); it now goes through _deserialize_payload like the sync path
  • Cache the per-task middleware chain (short TTL + instant same-process invalidation) instead of a storage read on every job dispatch
  • Skip the per-call/per-job option-dict building in enqueue / enqueue_many when no middleware is registered
  • Shared module-level thread pool for proxy reconstruction (was one ThreadPoolExecutor per proxy arg); webhook notify no-subscriber fast path; cloudpickle bound once; interception TypeRegistry per-type resolution cache

Rust core — storage, scheduler, worker

  • New jobs.has_deps column (centralized migration + idempotent backfill) so dequeue skips the dependency lookup entirely for the common no-dependency job (SQLite/Postgres + Redis)
  • reap_stale_jobs and purge_completed_with_ttl push the deadline / TTL predicate into SQL — they no longer load every candidate row's payload/result blobs just to filter in Rust
  • enqueue_batch uses a single chunked multi-row INSERT instead of N single-row inserts (chunked to stay under SQLite's bound-parameter limit)
  • Redis dequeue batches candidate loads with MGET and reuses the connection for dependency checks; single GIL acquisition on the worker failure path; active_queues borrows the queue list when nothing is paused

Verification

  • Dashboard: typecheck, biome lint, 106 vitest tests, production build
  • Rust: cargo check (default / postgres / redis / native-async), clippy --all-targets, 62 core tests (incl. 4 new regression tests for has_deps, reap, purge, batch chunking)
  • Python: ruff, mypy, 1005 passed / 6 skipped against a wheel rebuilt with the new core

Notes / scope

  • stats_by_queue was intentionally not narrowed to active statuses — it also feeds the dashboard's completed/failed/dead counts, so narrowing would be a correctness regression.
  • Deferred as low-value-vs-risk: adding a queue field to JobResult::Failure (13 construction sites across 3 crates), PyO3 module-object caching, and Redis per-queue/per-task counter re-modeling.
  • The first Rust commit also carries formatting-only churn: the pre-commit cargo fmt hook normalized pre-existing signature-wrapping drift in jobs.rs.

Summary by CodeRabbit

  • Performance Improvements

    • Faster dequeue and batch-enqueue paths; reduced allocations and skipped unnecessary dependency checks when possible.
  • Database & Migrations

    • Persisted job dependency flag added with migrations/backfill; archive/retry/purge/reap operations optimized to push filtering into the DB.
  • Middleware & SDK

    • Per-task middleware-chain caching with invalidation; fewer Python GIL ops in worker error handling; serializer-aware deserialization for executors.
  • Dashboard Improvements

    • Memoized components, controlled tab data loading, smarter "last refreshed" scheduling, and disabled refetch-on-focus.
  • Tests

    • New tests for middleware caching, reconstruction timeouts, serializers, batching, TTL purging, and dependency gating.

Review Change Stack

@coderabbitai

coderabbitai Bot commented May 29, 2026

Copy link
Copy Markdown

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 9955f56a-defa-4f2b-88e0-0908fc732ea0

📥 Commits

Reviewing files that changed from the base of the PR and between 7b864da and 437337f.

📒 Files selected for processing (2)
  • py_src/taskito/proxies/reconstruct.py
  • tests/resources/test_proxies.py

📝 Walkthrough

Walkthrough

Adds a persistent has_deps job flag and wires it through schema, models, migrations, enqueue/dequeue, and retry paths; batches inserts and Redis loads; pushes maintenance filters into SQL; introduces Python middleware-chain caching with in-process invalidation; consolidates Python GIL work; and applies UI memoization and query staleness tuning.

Changes

Dependency tracking and storage optimization

Layer / File(s) Summary
Job models and schema with has_deps field
crates/taskito-core/src/job.rs, crates/taskito-core/src/storage/models.rs, crates/taskito-core/src/storage/schema.rs, crates/taskito-core/src/storage/diesel_common/migrations.rs
Adds has_deps: bool to Job, JobRow, and NewJobRow; schema and migrations add column + backfill; NewJob/Job conversion and JobRow mapping populate has_deps.
Dequeue short-circuiting and batch loading
crates/taskito-core/src/storage/diesel_common/jobs.rs, crates/taskito-core/src/storage/redis_backend/jobs/dequeue.rs, crates/taskito-core/src/storage/sqlite/tests.rs
dequeue skips dependency checks when has_deps == false; Redis dequeue uses MGET to batch-load candidate job JSONs and resolves deps via connection-aware load; tests verify has_deps gating and stale-job behavior.
Storage migrations and job lifecycle updates
crates/taskito-core/src/storage/postgres/mod.rs, crates/taskito-core/src/storage/sqlite/mod.rs, crates/taskito-core/src/storage/diesel_common/archival.rs, crates/taskito-core/src/storage/diesel_common/dead_letter.rs
Migration runners execute backfill statements; archived jobs set has_deps = false; dead-letter retry propagates has_deps when re-enqueuing.
SQL-side filtering and batch operations for maintenance
crates/taskito-core/src/storage/diesel_common/jobs.rs, crates/taskito-core/src/storage/sqlite/tests.rs
purge_completed_with_ttl and reap_stale_jobs compute expiration/staleness in SQL and delete by ID sets; enqueue_batch inserts jobs in multi-row chunks; tests cover edge cases.
Enqueue and dequeue formatting adjustments
crates/taskito-core/src/storage/diesel_common/jobs.rs
Refactors enqueue_unique lookup, child-deletes predicate, and applies formatting-only signature/line-break changes.
Scheduler queue allocation reduction
crates/taskito-core/src/scheduler/poller.rs
Scheduler::active_queues returns Cow<[String]> to avoid allocations when no paused queues exist.

Python middleware caching and serialization optimization

Layer / File(s) Summary
Middleware chain caching infrastructure
py_src/taskito/mixins/decorators.py, py_src/taskito/mixins/middleware_admin.py, tests/core/test_middleware_chain_cache.py
Adds per-task _mw_chain_cache, _mw_disable_version, TTL-based caching and _bump_mw_disable_version(); _get_middleware_chain caches and filters by middleware_key. Tests validate caching and invalidation.
Enqueue middleware and allocation optimization
py_src/taskito/app.py
enqueue() skips building mutable options when no global middleware; enqueue_many() pre-validates and splits middleware vs no-middleware paths to reduce per-job allocations and produce parallel dedup-source arrays.
Serialization config and deserialization routing
py_src/taskito/async_support/executor.py, py_src/taskito/serializers.py, tests/worker/test_native_async.py
Executor deserializes via queue._deserialize_payload(task_name, payload_bytes) to honor per-task serializers; cloudpickle moved to module-level import; tests updated and new regression verifies per-task serializer path.
Python GIL acquisition optimization
crates/taskito-python/src/async_worker.rs, crates/taskito-python/src/py_worker.rs
Consolidates error formatting, cancellation detection, and should_retry computation into single GIL acquisitions to avoid repeated Python lock grabs.
Type registry and utility caching
py_src/taskito/interception/registry.py, py_src/taskito/proxies/reconstruct.py, py_src/taskito/webhooks.py, py_src/taskito/notes.py
Memoizes TypeRegistry.resolve() per concrete type; replaces executor-based reconstruction timeout with daemon-thread helper and tests for timeout semantics; optimizes webhook notify fast path and notes-size calculation.

Dashboard React Query and component performance

Layer / File(s) Summary
React Query staleness configuration
dashboard/src/features/circuit-breakers/hooks.ts, dashboard/src/features/resources/hooks.ts, dashboard/src/features/tasks/hooks.ts, dashboard/src/features/workers/hooks.ts, dashboard/src/lib/query-client.ts
Sets staleTime for circuit-breakers (60s), resources (30s), tasks/queues (60s), and workers (30s); disables refetchOnWindowFocus globally.
Component memoization and derived data caching
dashboard/src/components/ui/data-table.tsx, dashboard/src/features/jobs/components/job-overview-tab.tsx, dashboard/src/features/overview/components/throughput-sparkline.tsx
Wraps DataTable with React.memo, memoizes integration-links and sparkline data/paths, and uses useId for per-instance SVG gradient ids.
Job table row click and conditional tab loading
dashboard/src/features/jobs/components/job-table.tsx, dashboard/src/routes/jobs/$id.tsx
Memoizes row click callback and showErrorColumn; converts job detail tabs to controlled state and conditionally enables tab-specific data hooks.
LastRefreshed scheduling & theme init
dashboard/src/components/layout/last-refreshed.tsx, dashboard/src/providers/theme-provider.tsx
Replaces fixed 1s interval with adaptive scheduling for label changes and simplifies ThemeProvider initial resolved computation.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • ByteVeda/taskito#207: Refactors dead-letter retry operations in the same dead-letter flow that this PR also touches for has_deps propagation.

Poem

🐰 A rabbit found a job with ties,

It marked its deps with watchful eyes.
SQL trimmed the pruning chore,
Python cached the middleware store,
Dashboards hummed and clones ran less—hip, hop, hooray!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 67.90% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'perf: cross-layer performance pass (dashboard, Python, Rust)' clearly summarizes the main change—a comprehensive performance optimization pass across three layers of the codebase.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch perf/optimization-pass

Warning

Review ran into problems

🔥 Problems

Stopped waiting for pipeline failures after 30000ms. One of your pipelines takes longer than our 30000ms fetch window to run, so review may not consider pipeline-failure results for inline comments if any failures occurred after the fetch window. Increase the timeout if you want to wait longer or run a @coderabbit review after the pipeline has finished.


Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/taskito-core/src/storage/postgres/mod.rs`:
- Around line 127-129: The loop calling common_migrations::backfill_statements()
currently calls migration_alter(&mut conn, sql) which swallows errors; change
this so backfill failures are propagated instead of only logged: update
migration_alter to return a Result (or use an existing fallible variant), and in
the loop check its Result and return Err (or propagate with ?) including the SQL
and underlying error if any; ensure the enclosing function's signature returns
Result so failures abort startup/migration rather than being ignored.

In `@crates/taskito-core/src/storage/sqlite/mod.rs`:
- Around line 118-120: The backfill loop currently calls migration_alter(&mut
conn, sql) and swallows failures; change this to fail fast by checking
migration_alter's result and returning or propagating an error (or panicking)
when it fails so startup doesn't continue with bad data. Specifically, in the
loop over common_migrations::backfill_statements() in mod.rs, replace the blind
call to migration_alter with error handling that inspects its return (or update
migration_alter to return a Result if it doesn't) and propagate a failure (e.g.,
return Err(...) from the surrounding init function or call panic!) when
migration_alter indicates an error so has_deps/backfill never silently fails.

In `@dashboard/src/components/layout/last-refreshed.tsx`:
- Around line 28-32: The scheduled timeout in schedule() currently uses coarse
fixed bucket durations and can let labels stay stale; replace the fixed delay
calculation with a delay that aligns to the next label boundary based on age: if
age < 60_000 use 1000 - (age % 1000), else if age < 3_600_000 use 60_000 - (age
% 60_000), otherwise use 3_600_000 - (age % 3_600_000). Update the id =
setTimeout(...) call in the same schedule() block so setTick and the recursive
schedule() run when the next label change actually occurs.

In `@py_src/taskito/app.py`:
- Around line 695-708: The batch-builder currently builds per-job option arrays
from mixed scalar or list inputs (delay_list, metadata_list, notes_list,
expires_list, result_ttl_list, unique_keys, idempotency_keys) without verifying
that any provided lists match the number of jobs (count or len(args_list)),
which allows mismatched lengths to produce confusing downstream errors (e.g., in
_inner.enqueue_batch). Before constructing notes_encoded and the other _src
lists, validate each provided per-job list (those ending with _list or the
unique_keys/idempotency_keys lists) has length == count; if any mismatch raise a
clear ValueError indicating which parameter has the wrong length and what the
expected length (count) is so callers get immediate, actionable feedback.

In `@py_src/taskito/mixins/decorators.py`:
- Around line 156-158: The middleware filtering in _get_middleware_chain
currently only checks mw.name, so dashboard disables that use class-path keys
don't match unnamed middleware; convert the incoming disabled iterable to a set
of strings and when filtering the chain check both getattr(mw, "name", "") and
the middleware class path (mw.__class__.__module__ + "." +
mw.__class__.__name__) against that set, removing any mw that matches either key
so unnamed middleware can be disabled by class path as admin discovery does.

In `@py_src/taskito/proxies/reconstruct.py`:
- Around line 30-35: The current shared ThreadPoolExecutor (_RECONSTRUCT_POOL)
cannot stop already-running reconstruction work because
Future.result(timeout=...) only stops waiting and future.cancel() won't kill
started threads; change the reconstruction to run in an isolated process that
can be terminated on timeout: replace the ThreadPoolExecutor-based
submit+future.result(timeout=...) usage with a per-task multiprocessing.Process
(or spawn via multiprocessing.get_context("spawn")), start the process for the
reconstruction function, join with the desired timeout, and if the join timed
out call process.terminate() and process.join() to free the worker; ensure the
reconstruction caller checks for a terminated process and raises/returns a
timeout error. Reference symbols: _RECONSTRUCT_POOL, ThreadPoolExecutor,
Future.result, future.cancel.

In `@tests/core/test_middleware_chain_cache.py`:
- Around line 16-33: The test intermittently fails because the middleware-chain
cache TTL (_MW_CHAIN_TTL) can expire during the loop; to fix, in
test_middleware_chain_caches_disable_reads set or monkeypatch the TTL or the
module's monotonic time so the TTL cannot elapse during the 50 iterations (e.g.,
assign a large value to Queue._MW_CHAIN_TTL or the module-level _MW_CHAIN_TTL,
or patch time.monotonic to a fixed value) before creating the Queue and running
the loop, then restore the original value if needed; target symbols:
test_middleware_chain_caches_disable_reads, Queue, _get_middleware_chain, and
_MW_CHAIN_TTL.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: b8211a09-c927-42aa-a142-e459ca9d43bb

📥 Commits

Reviewing files that changed from the base of the PR and between b2e2b6f and b999830.

📒 Files selected for processing (37)
  • crates/taskito-core/src/job.rs
  • crates/taskito-core/src/scheduler/poller.rs
  • crates/taskito-core/src/storage/diesel_common/archival.rs
  • crates/taskito-core/src/storage/diesel_common/dead_letter.rs
  • crates/taskito-core/src/storage/diesel_common/jobs.rs
  • crates/taskito-core/src/storage/diesel_common/migrations.rs
  • crates/taskito-core/src/storage/models.rs
  • crates/taskito-core/src/storage/postgres/mod.rs
  • crates/taskito-core/src/storage/redis_backend/jobs/dequeue.rs
  • crates/taskito-core/src/storage/schema.rs
  • crates/taskito-core/src/storage/sqlite/mod.rs
  • crates/taskito-core/src/storage/sqlite/tests.rs
  • crates/taskito-python/src/async_worker.rs
  • crates/taskito-python/src/py_worker.rs
  • dashboard/src/components/layout/last-refreshed.tsx
  • dashboard/src/components/ui/data-table.tsx
  • dashboard/src/features/circuit-breakers/hooks.ts
  • dashboard/src/features/jobs/components/job-overview-tab.tsx
  • dashboard/src/features/jobs/components/job-table.tsx
  • dashboard/src/features/overview/components/throughput-sparkline.tsx
  • dashboard/src/features/resources/hooks.ts
  • dashboard/src/features/tasks/hooks.ts
  • dashboard/src/features/workers/hooks.ts
  • dashboard/src/lib/query-client.ts
  • dashboard/src/providers/theme-provider.tsx
  • dashboard/src/routes/jobs/$id.tsx
  • py_src/taskito/app.py
  • py_src/taskito/async_support/executor.py
  • py_src/taskito/interception/registry.py
  • py_src/taskito/mixins/decorators.py
  • py_src/taskito/mixins/middleware_admin.py
  • py_src/taskito/notes.py
  • py_src/taskito/proxies/reconstruct.py
  • py_src/taskito/serializers.py
  • py_src/taskito/webhooks.py
  • tests/core/test_middleware_chain_cache.py
  • tests/worker/test_native_async.py

Comment thread crates/taskito-core/src/storage/postgres/mod.rs
Comment thread crates/taskito-core/src/storage/sqlite/mod.rs
Comment thread dashboard/src/components/layout/last-refreshed.tsx Outdated
Comment thread py_src/taskito/app.py
Comment thread py_src/taskito/mixins/decorators.py Outdated
Comment thread py_src/taskito/proxies/reconstruct.py Outdated
Comment thread tests/core/test_middleware_chain_cache.py Outdated
@pratyush618

Copy link
Copy Markdown
Collaborator Author

Addressed all 7 review findings:

  • Backfill swallowing errors (sqlite/mod.rs, postgres/mod.rs) — backfill now runs via diesel::sql_query(*sql).execute()? so a failure aborts startup instead of being logged. fix(storage): fail fast on has_deps backfill errors
  • Middleware disable keys (decorators.py) — extracted a single middleware_key() / middleware_class_path() helper in middleware.py and routed chain filtering + admin discovery + the dashboard handler through it (was triplicated). The base TaskMiddleware.__init__ already auto-assigns the class path to name, so this mainly hardens the bypassed-__init__ edge case while removing the duplication. refactor: extract shared middleware_key/class_path helper
  • enqueue_many list lengths (app.py) — every per-job list is now length-checked against count up front with a clear per-field error. fix: validate per-job list lengths in enqueue_many
  • Reconstruction timeout (reconstruct.py) — added future.cancel() on timeout to free still-queued work. A process pool isn't viable here: proxy reconstruction returns thread-local/non-picklable objects (file handles, sessions) that can't cross a process boundary back, so the thread pool is the correct primitive and the timeout is a wait-bound by design (documented; handlers should bound their own I/O). fix(proxies): cancel still-queued reconstruction on timeout
  • LastRefreshed cadence (last-refreshed.tsx) — timeout now fires at the next label boundary (bucket - age % bucket). perf(dashboard): align LastRefreshed timeout to label boundary
  • Cache test flakiness (test_middleware_chain_cache.py) — _MW_CHAIN_TTL is monkeypatched large so the read-count assertion is timing-independent. test: pin middleware-chain TTL for deterministic cache assertion

Verified: cargo check (default + postgres) + clippy + 62 core tests; ruff + mypy clean; 213 affected Python tests (middleware cache/toggles, batch, native-async, resources) pass on a rebuilt wheel; dashboard lint + typecheck + 106 vitest.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
py_src/taskito/dashboard/handlers/middleware.py (1)

50-53: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Use middleware_key() for PUT registration checks

Line 51 validates against raw mw.name, which can diverge from the keys returned by GET/list endpoints (now using middleware_key). This can incorrectly 404 valid middleware toggles.

Suggested fix
-    names = {getattr(mw, "name", "") for mw in base_chain}
+    names = {middleware_key(mw) for mw in base_chain}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@py_src/taskito/dashboard/handlers/middleware.py` around lines 50 - 53, The
PUT registration check currently builds names from mw.name which may differ from
the canonical keys; update the check to use middleware_key(mw) instead: when
constructing names from base_chain (from queue._global_middleware and
queue._task_middleware.get(task_name, [])) call middleware_key on each
middleware and compare mw_name against that set, leaving the _NotFound raising
logic (and variables mw_name, task_name, base_chain) unchanged so valid
middleware keys are not incorrectly 404ed.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@py_src/taskito/dashboard/handlers/middleware.py`:
- Around line 50-53: The PUT registration check currently builds names from
mw.name which may differ from the canonical keys; update the check to use
middleware_key(mw) instead: when constructing names from base_chain (from
queue._global_middleware and queue._task_middleware.get(task_name, [])) call
middleware_key on each middleware and compare mw_name against that set, leaving
the _NotFound raising logic (and variables mw_name, task_name, base_chain)
unchanged so valid middleware keys are not incorrectly 404ed.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 7d5f4b07-9795-4e16-8d51-89de2a2f4265

📥 Commits

Reviewing files that changed from the base of the PR and between b999830 and 7b864da.

📒 Files selected for processing (10)
  • crates/taskito-core/src/storage/postgres/mod.rs
  • crates/taskito-core/src/storage/sqlite/mod.rs
  • dashboard/src/components/layout/last-refreshed.tsx
  • py_src/taskito/app.py
  • py_src/taskito/dashboard/handlers/middleware.py
  • py_src/taskito/middleware.py
  • py_src/taskito/mixins/decorators.py
  • py_src/taskito/mixins/middleware_admin.py
  • py_src/taskito/proxies/reconstruct.py
  • tests/core/test_middleware_chain_cache.py
🚧 Files skipped from review as they are similar to previous changes (5)
  • tests/core/test_middleware_chain_cache.py
  • dashboard/src/components/layout/last-refreshed.tsx
  • py_src/taskito/app.py
  • py_src/taskito/mixins/decorators.py
  • py_src/taskito/proxies/reconstruct.py

A shared ThreadPoolExecutor force-joins its workers at interpreter exit
(CPython issue 36780), so a hung reconstruction would block shutdown, and a
bounded pool can be exhausted by hung tasks. A process pool can't return the
non-picklable live objects reconstruction produces. Run each timed
reconstruction on a daemon thread joined with the timeout instead: an overrun
is abandoned without blocking the worker or shutdown. Adds timeout tests.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant